package ants;
import java.util.HashSet;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Predicate;


/**
 * This class contains higher order functions (HOF)
 */
public class HOF {
  
  /**
   * Function_1<T1, T2> is replaced by com.google.common.base.Function. Leaving
   * it here as private as a record of what it was.
   */
  @SuppressWarnings("unused") interface Function_1<T1, T2> {
    
    public T2 apply(T1 e);
    
  }
  
  
  public interface Function_1_void<T> {
    
    public void apply(T e);
    
  }
  
  
  public static abstract class Function_2<T1, T2, T3> {
    
    abstract public T3 apply2(T1 e1, T2 e2);
    
    
    public Function<T2, T3> curry(final T1 arg1) {
      return new Function<T2, T3>( ) {
        
        @Override
        public T3 apply(T2 arg2) {
          return apply2(arg1, arg2);
        }
        
      };
    }
    
    
    public Predicate<T2> curryToPredicate(final T1 arg1) {
      return new Predicate<T2>( ) {
        
        @Override
        public boolean apply(T2 arg2) {
          return (Boolean) apply2(arg1, arg2);
        }
        
      };
    }
    
  }
  
  
  
  /**
   * Is there an element in elements that has the property hasProperty?
   * 
   * To see these, first imagine that "<T>" after "static" is removed and T is
   * replaced by MapLocation everywhere.
   * 
   * @param function
   *          the property being tested. apply() returns true or false depending
   *          on whether its argument has this property.
   * 
   * @param orderedDestinations
   *          the collection of elements being searched.
   * 
   * @return true or false depending on whether an element of elements has the
   *         property hasProperty
   */
  public static <T> boolean exists(Function<T, Boolean> function,
                                   Set<T> orderedDestinations) {
    for (final T e: orderedDestinations)
      if (function.apply(e)) return true;
    return false;
  }
  
  
//  /**
//   * All elements in elements that have the property hasProperty?
//   * 
//   * @param hasProperty
//   *          the property being tested. apply() returns true or false depending
//   *          on whether its argument has this property.
//   * 
//   * @param elements
//   *          the collection of elements being searched.
//   * 
//   * @return the subset of elements (perhaps all) with the property hasPropetry
//   */
//  public static <T> ImmutableList<T> filter(Function<T, Boolean> hasProperty,
//                                            Iterable<T> elements) {
//    final ImmutableList.Builder<T> builder = ImmutableList.builder( );
//    for (final T e: elements)
//      if (hasProperty.apply(e)) builder.add(e);
//    return builder.build( );
//  }
//  
//  
//  public static <T1, T2> ImmutableList<T2> map(Function<T1, T2> mappingFunction,
//                                               Iterable<T1> elements) {
//    final ImmutableList.Builder<T2> builder = ImmutableList.builder( );
//    for (final T1 e: elements)
//      builder.add(mappingFunction.apply(e));
//    return builder.build( );
//  }
//  
//  
//  // Don't understand why T1[] doesn't work as an Iterable here.
//  public static <T1, T2> ImmutableList<T2> map(Function<T1, T2> mappingFunction,
//                                               T1[] elements) {
//    return map(mappingFunction, Arrays.asList(elements));
//  }
  
  
  /**
   * Applies the mappingFunction to all elements in elements.
   * 
   * @param mappingFunction
   *          the function to be applied to elements of elements.
   * 
   * @param elements
   *          the collection of elements being operated on.
   * 
   * @return void
   */
  public static <T> void mapToVoid(Function_1_void<T> mappingFunction,
                                   Iterable<T> elements) {
    for (final T e: elements)
      mappingFunction.apply(e);
  }
  
  public static <T1, T2> Set<T2> map(Function_1<T1, T2> mappingFunction, Iterable<T1> elements){
//  maps an Iterable collection of T1s element-by-element to a Set of T2s.
   Set <T2> result = new HashSet<T2>();
   for (final T1 e: elements)
       result.add(mappingFunction.apply(e));
   return result;
 }
  
}
